#include "raytracer.h"
#include "material.h"
#include "vectors.h"
#include "argparser.h"
#include "raytree.h"
#include "utils.h"
#include "mesh.h"
#include "face.h"
#include "primitive.h"
#include "photon_mapping.h"
#include "MersenneTwister.h"
#include<math.h>
using namespace std;
// ===========================================================================
// casts a single ray through the scene geometry and finds the closest hit
bool RayTracer::CastRay(Ray &ray, Hit &h, bool use_rasterized_patches) const {
  bool answer = false;

  // intersect each of the quads
  for (int i = 0; i < mesh->numOriginalQuads(); i++) {
    Face *f = mesh->getOriginalQuad(i);
    if (f->intersect(ray,h,args->intersect_backfacing)) answer = true;
  }

  // intersect each of the primitives (either the patches, or the original primitives)
  if (use_rasterized_patches) {
    for (int i = 0; i < mesh->numRasterizedPrimitiveFaces(); i++) {
      Face *f = mesh->getRasterizedPrimitiveFace(i);
      if (f->intersect(ray,h,args->intersect_backfacing)) answer = true;
    }
  } else {
    int num_primitives = mesh->numPrimitives();
    for (int i = 0; i < num_primitives; i++) {
      if (mesh->getPrimitive(i)->intersect(ray,h)) answer = true;
    }
  }
  return answer;
}

// ===========================================================================
// does the recursive (shadow rays & recursive rays) work
Vec3f RayTracer::TraceRay(Ray &ray, Hit &hit, int bounce_count) const {

  // First cast a ray and see if we hit anything.
  hit = Hit();
  bool intersect = CastRay(ray,hit,false);
  
  // if there is no intersection, simply return the background color
  if (intersect == false) {
    return Vec3f(srgb_to_linear(args->background_color.r()),
		 srgb_to_linear(args->background_color.g()),
		 srgb_to_linear(args->background_color.b()));
    
  }

  // otherwise decide what to do based on the material
  Material *m = hit.getMaterial();
  assert (m != NULL);

  // rays coming from the light source are set to white, don't bother to ray trace further.
  if (m->getEmittedColor().Length() > 0.001) {
    return Vec3f(1,1,1);
  } 
 
  
  Vec3f normal = hit.getNormal();
  Vec3f point = ray.pointAtParameter(hit.getT());
  Vec3f answer;

  // ----------------------------------------------
  // ambient light
  Vec3f diffuse_color = m->getDiffuseColor(hit.get_s(),hit.get_t());
  if (args->gather_indirect) {
    // photon mapping for more accurate indirect light
    answer = diffuse_color * photon_mapping->GatherIndirect(point, normal, ray.getDirection());
  } else {
    // the usual ray tracing hack for indirect light
    answer = diffuse_color * args->ambient_light;
  }      

  // ----------------------------------------------
  // add contributions from each light that is not in shadow
  

	int num_lights = mesh->getLights().size();
	for (int i = 0; i < num_lights; i++) 
	{

		Face *f = mesh->getLights()[i];
		Vec3f pointOnLight = f->computeCentroid();
		Vec3f lightColor = f->getMaterial()->getEmittedColor() * f->getArea();
		Vec3f dirToLight = pointOnLight - point;
		double dist = dirToLight.Length();
		dirToLight.Normalize();
		lightColor /= (float)(M_PI*dist*dist);
		 
		// ===========================================
		// ASSIGNMENT:  ADD SHADOW & SOFT SHADOW LOGIC
		// ===========================================

		/*Find the point to be shaded
		For every light,
		Construct ray from point to light      //dirToLight
		For every object
		find intersection of ray with object
		If no objects between point and light
		Add contribution from light*/
		
		if(args->num_shadow_samples > 0)
		{
			vector<Vec3f> points_on_light;
			vector<Vec3f> dirs_to_light;
			vector<Vec3f> answers;
			Vec3f average=Vec3f(0,0,0);
			Vec3f averages=Vec3f(0,0,0);
			double rays_cast=0;
			
			for(int i=0; i<args->num_shadow_samples; i++)
			{
				points_on_light.push_back(f->RandomPoint()); //
				
				dirs_to_light.push_back(points_on_light[i]-point); //
				double dis = dirs_to_light[i].Length(); //
				dirs_to_light[i].Normalize(); //
				Ray r(point,dirs_to_light[i]);//points_on_light[i],dirs_to_light[i]); 
				
				Vec3f color = f->getMaterial()->getEmittedColor() * f->getArea(); //
				color /= (float)(M_PI*dis*dis); //
				Hit a_hit = Hit();
				if(CastRay(r,a_hit,false))
				{
					Vec3f dur=r.getDirection()*a_hit.getT();
					double len = dur.Length();
					
					Vec3f dur2=ray.getDirection()*hit.getT();
					double len2 = dur2.Length();
					
					double d = sqrt( (points_on_light[i].x()-point.x())*(points_on_light[i].x()-point.x()) +\
									(points_on_light[i].y()-point.y())*(points_on_light[i].y()-point.y()) +\
									(points_on_light[i].z()-point.z())*(points_on_light[i].z()-point.z()) );
					
					if( len>=(d-(d*0.0001)) && len<=(d+(d*0.0001)) )
					{				
						average=m->Shade(ray,hit,dirs_to_light[i],color,args);
						averages+=average;
						rays_cast++;
					}
					
				}		
			}
			answer += averages*(rays_cast/(args->num_shadow_samples*args->num_shadow_samples));
			
			
	  }
	  
	  else
		answer += m->Shade(ray,hit,dirToLight,lightColor,args);
  
  }
  
    
  
  //point = source
  //normal = dir
      
  // ----------------------------------------------
  // add contribution from reflection, if the surface is shiny
  Vec3f reflectiveColor = m->getReflectiveColor();
  //Ray (const Vec3f &orig, const Vec3f &dir)
  
  Hit b_hit=Hit();
  Vec3f v = ray.getDirection();//*hit.getT();
  normal.Normalize();
  Vec3f reflect = v - 2*(v.Dot3(normal))*normal;
  //reflect.Normalize();
  Ray b_ray(point,reflect); 
  RayTree::AddReflectedSegment(b_ray,0,hit.getT());
  //if(bounce_count>0)
	//cout<<bounce_count<<endl;
	
  if(bounce_count<args->num_bounces )//&& CastRay(b_ray,b_hit,true))
  {
	answer += reflectiveColor* (TraceRay(b_ray,b_hit,bounce_count+1) );
  }


  // =================================
  // ASSIGNMENT:  ADD REFLECTIVE LOGIC
  // =================================
  
  
  return answer; 
}




